package com.gframework.boot.mvc.controller.protocal.basic;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.activation.FileTypeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;


/**
 * controller返回文件对象信息封装类.
 * <p>
 * 在使用了 {@link BasicProtocalControllerAOP} AOP的情况下，只要controller返回了
 * 本类对象，就意味着要进行文件的下载或者在浏览器预览图片或pdf等浏览器支持预览的文件。
 * </p>
 * <p>
 * 但是如果你只想返回文本信息，你可以直接返回对象或者封装成 {@link ServerResponse} 类返回。
 * </p>
 * <strong>本类是线程不安全的</strong>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * 
 * @see BasicProtocalControllerAOP
 * @see ServerResponse
 */
@SuppressWarnings("serial")
public class FileResponse extends ServerResponse {
	private static final Logger logger = LoggerFactory.getLogger(FileResponse.class);
	/**
	 * 是否在浏览器显示.<br>
	 * 需要浏览器支持
	 */
	private boolean showInBrowser = false;
	/**
	 * 通常来说如果只是在浏览器显示不需要设置filename，但是如果
	 * 浏览器不能够识别此文件类型（不支持显示），那么就会变成下载。
	 */
	private String fileName ;
	/**
	 * 数据长度，这是一个可选的
	 */
	private Long dataLength ;
	/**
	 * 下载文件类型，通常来说如果是下载文件，不用做额外设置。但是如果是预览文件，mimeType会比较重要。
	 */
	private String mimeType ;
	
	/**
	 * 获取数据的输入流对象
	 */
	private InputStream dataInputStream ;
	
	/**
	 * 根据文件创建一个FileResponse对象
	 * @param file 文件对象
	 * @param fileName 文件名称
	 * @param mimeType 文件MIME类型
	 * @return 返回本类实例化对象
	 * @throws NullPointerException file为null
	 * @throws FileNotFoundException 如果文件不存在
	 */
	public static FileResponse create(File file,String fileName,String mimeType) throws FileNotFoundException {
		FileResponse f = new FileResponse();
		f.setMimeType(mimeType);
		f.setFileName(fileName);
		f.setFile(file);
		return f;
	}
	/**
	 * 根据文件创建一个FileResponse对象
	 * @param file 文件对象
	 * @param fileName 文件名称
	 * @return 返回本类实例化对象
	 * @throws NullPointerException file为null
	 * @throws FileNotFoundException 如果文件不存在
	 */
	public static FileResponse create(File file,String fileName) throws FileNotFoundException {
		return create(file,fileName,getMimeType(fileName));
	}
	/**
	 * 根据文件创建一个FileResponse对象
	 * @param file 文件对象
	 * @return 返回本类实例化对象
	 * @throws NullPointerException file为null
	 * @throws FileNotFoundException 如果文件不存在
	 */
	public static FileResponse create(File file) throws FileNotFoundException {
		return create(file,file.getName());
	}
	
	/**
	 * 根据输入流创建一个FileResponse对象
	 * @param input 输入流对象
	 * @param fileName 文件名称
	 * @return 返回本类实例化对象
	 * @throws NullPointerException input为null
	 */
	public static FileResponse create(InputStream input,String fileName) {
		return create(input,fileName,null);
	}
	/**
	 * 根据输入流创建一个FileResponse对象
	 * @param input 输入流对象
	 * @param fileName 文件名称
	 * @param mimeType 文件MIME类型
	 * @return 返回本类实例化对象
	 * @throws NullPointerException input为null
	 */
	public static FileResponse create(InputStream input,String fileName,String mimeType) {
		FileResponse f = new FileResponse();
		f.setInputStream(input);
		f.setMimeType(mimeType);
		f.setFileName(fileName);
		return f;
	}
	
	/**
	 * 根据二进制数据创建一个FileResponse对象
	 * @param data 二进制数据
	 * @param fileName 文件名称
	 * @return 返回本类实例化对象
	 * @throws NullPointerException data为null
	 */
	public static FileResponse create(byte[] data,String fileName) {
		return create(data,fileName,null);
	}
	/**
	 * 根据二进制数据创建一个FileResponse对象
	 * @param data 二进制数据
	 * @param fileName 文件名称
	 * @param mimeType 文件MIME类型
	 * @return 返回本类实例化对象
	 * @throws NullPointerException data为null
	 */
	public static FileResponse create(byte[] data,String fileName,String mimeType) {
		FileResponse f = new FileResponse();
		f.setBytes(data);
		f.setMimeType(mimeType);
		f.setFileName(fileName);
		return f;
	}
	
	protected FileResponse() {
	}
	
	/**
	 * 是否在浏览器预览（需要浏览器支持），默认false
	 * @return true表示在浏览器预览，否则下载
	 */
	public boolean isShowInBrowser() {
		return this.showInBrowser;
	}
	
	/**
	 * 设置 是否在浏览器预览（需要浏览器支持），默认false
	 * @param showInBrowser true表示在浏览器预览，否则下载
	 * @return 返回当前对象
	 */
	public FileResponse setShowInBrowser(boolean showInBrowser) {
		this.showInBrowser = showInBrowser;
		return this;
	}
	
	/**
	 * 取得文件名称.<br>
	 * @return 返回文件名
	 */
	@Nullable
	public String getFileName() {
		return this.fileName;
	}
	/**
	 * 设置文件名称.<br>
	 * <p>
	 * 通常来说如果只是在浏览器显示不需要设置filename，但是如果
	 * 浏览器不能够识别此文件类型（不支持显示），那么就会变成下载。
	 * </p>
	 * <strong>设置文件名后，如果mimeType为null，则会根据文件名自动识别mimeType</strong>
	 * @param fileName 文件名
	 * @return 返回当前对象
	 */
	public FileResponse setFileName(@Nullable String fileName) {
		this.fileName = fileName;
		if (this.mimeType == null) {
			this.mimeType = getMimeType(fileName);
		}
		return this;
	}
	
	
	/**
	 * 设置文件类型，如果不设置，则会使用默认的。如果传入得是一个File对象，则会自动根据后缀名判断文件类型.<br>
	 * 下载文件类型，通常来说如果是下载文件，不用做额外设置。但是如果是预览文件，mimeType会比较重要。
	 * @param mimeType 文件类型
	 * @see FileTypeMap
	 * @return 返回当前对象
	 */
	public FileResponse setMimeType(String mimeType) {
		this.mimeType = mimeType;
		return this ;
	}
	
	/**
	 * 取得文件类型.<br>
	 * @return 文件类型
	 */
	public String getMimeType() {
		return this.mimeType;
	}
	

	/**
	 * 设置要下载或显示的文件.
	 * <p>
	 * 设置文件对象后，如果当前对象还没有设置mimeType或fileName,则会自动识别mimeType和fileName。
	 * </p>
	 * 
	 * @param file 要输出的文件，如果为null则抛出异常
	 * @throws FileNotFoundException 如果文件不存在
	 * @throws NullPointerException 如果文件对象为null
	 * @return 返回当前对象
	 */
	public FileResponse setFile(File file) throws FileNotFoundException {
		if (file == null)
			throw new NullPointerException("file 不能为null");
		
		this.dataInputStream = new FileInputStream(file);
		String name = file.getName();
		if (this.fileName == null) {
			this.fileName = name;
		}
		if (this.mimeType == null) {
			this.mimeType = getMimeType(name);
		}
		this.dataLength = file.length();
		return this ;
	}
	
	/**
	 * 设置要输出的文件输出流对象.<br>
	 * <strong>注意：在数据被发送到客户端后，此流会被直接关闭无法在使用。</strong>
	 * 
	 * @param inputStream 输出流对象，如果输出流已经读到结尾或关闭，则会导致错误发生，但是这一问题无法在此方法上被发现。
	 * @throws NullPointerException 如果文件对象为null
	 * @return 返回当前对象
	 */
	public FileResponse setInputStream(InputStream inputStream) {
		if (inputStream == null)
			throw new NullPointerException("inputStream 不能为null");
		
		this.dataInputStream = inputStream ;
		return this;
	}
	
	/**
	 * 设置要输出的二进制信息.
	 * 
	 * @param data 二进制信息
	 * @throws NullPointerException 如果文件对象为null
	 * @return 返回当前对象
	 */
	public FileResponse setBytes(byte[] data) {
		if (data == null)
			throw new NullPointerException("data 不能为null");
		this.dataInputStream = new ByteArrayInputStream(data);
		return this;
	}
	
	
	/**
	 * 设置数据长度[可惜]
	 * @param dataLength 数据长度
	 * @return 返回当前对象
	 */
	public FileResponse setDataLength(@Nullable Long dataLength) {
		if (dataLength != null && dataLength > 0) {
			this.dataLength = dataLength;
		}
		return this;
	}
	
	/**
	 * 取得数据长度
	 * @return 数据长度，null或一个大于0的值，不会小于0
	 */
	@Nullable
	public Long getDataLength() {
		return this.dataLength;
	}
	
	
	
	/**
	 * 获取下载文件输入流对象
	 * @return 返回InputStream
	 */
	InputStream getDataInputStream() {
		return this.dataInputStream;
	}
	
	/**
	 * 设置要发送的文件数据.
	 * <strong>注意:通过此方法进行设置不会引发异常，这会导致可能发生的null异常或文件找不到的错误</strong>
	 * @param data 可以是File、InputStream、byte[]。其他类型则会直接按照字符串输出
	 * @throws IOException 在IO流上出现错误，如文件不存在等
	 */
	@Override
	public void setData(Object data) {
		if (data == null) {
			if (logger.isWarnEnabled()) {
				logger.warn("通过setData(Object)方法传入了null值。只是不应该发送的情况。请检查代码最好在调用出就控制好null问题！");
			}
			return ;
		}
		
		if (data instanceof File) {
			try {
				this.setFile((File)data);
			} catch (FileNotFoundException e) {
				if (logger.isErrorEnabled()) {
					logger.error("找不到文件：" + ((File)data).getName(),e);
				}
			}
		} else if (data instanceof InputStream) {
			this.setInputStream((InputStream)data);
		} else if (data.getClass() ==  byte[].class) {
			this.setBytes((byte[])data);
		} else {
			this.setBytes(data.toString().getBytes());
		}
	}
	
	/**
	 * 根据名称取得ContentType类型，没有返回null
	 */
	public static String getMimeType(String fileName) {
		if (fileName == null) {
			return null ;
		} else {
			return FileTypeMap.getDefaultFileTypeMap().getContentType(fileName);
		}
	}
}
